<!-- 图片保护性拉伸和截图显示 -->
<template>
	<image v-if="iSrc" :src="iSrc" mode="aspectFit" :style="[{width: frame.width * wScale + 'px', height: frame.height * hScale + 'px'}]"></image>
	<CaCanvas v-else-if="step === 1" :width="sWidth" :height="sHeight" :drawAction="drawAction" hideCanvasing/>
	<div v-else-if="step > 1" class="center-column" style="position: relative;z-index: 1;">
		<div v-for="(child, index) in material" :key="index" style="display: flex;">
			<div v-for="(item, idx) in child" :key="idx">
				<div v-if="item.src" :style="[{
					position: 'relative', display: 'flex',
					width: getRealSize(index, idx, 'width'), 
					height: getRealSize(index, idx, 'height'),
					// backgroundColor: `rgba(${index * 80}, ${idx * 80}, ${index * idx * 25}, 1)`,
					// backgroundImage: `url(${item.src})`,
					// backgroundSize: 'contain',
					// backgroundPosition: item.position,
					// backgroundRepeat: getRepeat(index, idx),
				}, reviseStyle(item.position)]">
					<!-- 使用背景图不能完全填满 -->
					<image :src="item.src" style="width: 100%;height: 100%;position: absolute;left: 0;top: 0;z-index: 0;" mode="scaleToFill"></image>
					<div v-if="item.key === 'center'" id="center" :style="[{padding: getContentPadding()}]">
						<slot :width="item.frame.width * wScale" :height="item.frame.height * hScale" :refresh="observeSize"></slot>
					</div>
					<div v-else-if="item.key === 'right-bottom'" :style="[{width: item.frame.width * wScale + 'px', height: item.frame.height * hScale + 'px', zIndex: 1}]">
						<slot name="right-bottom" :width="item.frame.width * wScale" :height="item.frame.height * hScale"></slot>
					</div>
					<!-- 微信小程序不支持动态命名下传值。如需传值仿照right-bottom的写法 -->
					<div v-else :style="[{width: item.frame.width * wScale + 'px', height: item.frame.height * hScale + 'px', zIndex: 1}]">
						<slot :name="item.key"></slot>
					</div>
				</div>
			</div>
		</div>
		<div v-if="hasCover" style="position: absolute;top: 0;left: 0;right: 0;z-index: 2;">
			<slot name="cover"></slot>
		</div>
	</div>
</template>

<script>
	/**
	 * 
	 * 存在frame，表示是截图显示
	 * 
	 * 保护性拉伸
	 * 不需绘图，但是要截取
	 * 将原图分为四角四边和中间部分
	 * 
	 * 1，原图等比例缩放，保证不变形
	 * 2，四个角完全不缩放，原图如何就如何
	 * 3，四条边则在相应的方向上伸缩
	 * 4，中间部分留下并任意填充，无限平铺
	 * 
	 * 保护区域。相对于原图的数值。最大值为原图的一半
	 * side：上左下右的值完全一致。优先级低于上左下右
	 * top：上边的保护区域
	 * left：左边的保护区域
	 * bottom：下边的保护区域
	 * right：右边的保护区域
	 */
	
	// margin的修正值。经测试，该值为1完美，为0时总有间隙
	const revise = 1
	
	import base from '@/components/index.vue'
	
	export default {
		extends: base,
		// 回调中间部分的背景图片
		emits: ['center'],
		props: {
			// 图片对象
			src: [String, Object],
			
			/**
			 * 以下三项是传入的宽高值，以缩放
			 * 如果没有将获取父组件的宽度
			 * 微信小程序无法直接获取父组件的宽度，所以建议传值
			 */
			width: [String, Number],
			height: [String, Number],
			size: [String, Number],
			
			/**
			 * 该frame存在，表示要截图显示
			 * 需要截图的部分。分别是相对于原图的数据
			 * 
			 *  x: 0, // 可以是百分数
				y: 0, // 可以是百分数
				width: 0,
				height: 0
			 */
			frame: Object,
			
			/**
			 * 保护区域 - 数值都是相对于原始图片来说的
			 * side：四个角都相同。优先级低于四边
			 * 最多等于图片宽高的一半
			 */
			side: {
				type: [String, Number],
				default: 100
			},
			// 上边的保护高度
			top: [String, Number],
			// 左边的保护宽度
			left: [String, Number],
			// 下边的保护高度
			bottom: [String, Number],
			// 左边的保护宽度
			right: [String, Number],
			
			// 是否有覆盖内容
			hasCover: Boolean,
			
			// 宽度或者高度不固定的内容，通过这个方法来获取最新的宽高
			updateSize: Function,
			
			/**
			 * 为了让四条边能够完美的渲染平铺，需监听内部元素变化
			 * 可以监听到offsetLeft(marginLeft)和offsetTop(marginTop)，确无法监听到marginBottom和marginRight
			 * 所以如果有marginBottom和marginRight，需指定传入
			 * top和left如果有值，以其值为准，没有值，以默认获取的为准
			 */
			observeMargin: {
				type: Object,
				default: () => {
					return {
						top: 0,
						left: 0,
						bottom: 0,
						right: 0
					}
				}
			}
		},
		data() {
			return {
				revise,
				// 截取的内容
				material: [
					[
						{ // 左上角
							key: 'left-top', data: [],
							src: '', position: 'bottom right',
							frame: {x: 0, y: 0, width: 0, height: 0},
						},
						{ // 上边
							key: 'top', data: [],
							src: '', position: 'bottom',
							frame: {x: 0, y: 0, width: 0, height: 0}
						},
						{ // 右上角
							key: 'right-top', data: [],
							src: '', position: 'bottom left',
							frame: {x: 0, y: 0, width: 0, height: 0}
						}
					],
					[
						{ // 左边
							key: 'left', data: [],
							src: '', position: 'right',
							frame: {x: 0, y: 0, width: 0, height: 0}
						},
						{ // 中心
							key: 'center', data: [],
							src: '', position: 'center',
							frame: {x: 0, y: 0, width: 0, height: 0}
						},
						{ // 右边
							key: 'right', data: [],
							src: '', position: 'left',
							frame: {x: 0, y: 0, width: 0, height: 0}
						}
					],
					[
						{ // 左下角
							key: 'left-bottom', data: [],
							src: '', position: 'top right',
							frame: {x: 0, y: 0, width: 0, height: 0}
						},
						{ // 下边
							key: 'bottom', data: [],
							src: '', position: 'top',
							frame: {x: 0, y: 0, width: 0, height: 0}
						},
						{ // 右下角
							key: 'right-bottom', data: [],
							src: '', position: 'top left',
							frame: {x: 0, y: 0, width: 0, height: 0}
						}
					]
				],
				// 截图显示的src
				iSrc: '',
				/**
				 * 加载状态
				 * 0：未初始化
				 * 1：初始化中，正在绘原图并截图
				 * 2：绘制九个原图。该方案舍弃
				 * 3：获取九个原图完毕，开始展开
				 */
				step: 0,
				// 原图的宽高
				sWidth: 0,
				sHeight: 0,
				// 计数器
				count: 0,
				// 实际显示的宽度缩放值
				wScale: 1,
				// 实际显示的高度缩放值
				hScale: 1,
				// 中间内容的宽高
				c_width: 0,
				c_height: 0
			}
		},
		computed: {
			// 绘主图的ID
			canvasId() {
				const number = parseInt(Math.random() * 1000)
				return `myCanvas${number}`
			}
		},
		mounted() {
			// 获取原始图片信息
			uni.getImageInfo({
				src: this.src,
				success: (res) => {
					/**
					 * path: 返回图片的本地路径
					 * 下面两个，H5上没有
					 * orientation: 返回图片的方向
					 * type: 返回图片的格式
					 */
					const {width, height, path, orientation, type} = res
					this.sWidth = width
					this.sHeight = height
					// 获取显示的缩放值
					this.getScaleValue()
					// 开始绘图
					this.step = 1
				}
			})
		},
		methods: {
			/**
			 * 监听尺寸变化
			 * H5会自动刷新，小程序需延迟执行刷新
			 * 可以通过中间部分的插槽传递的参数refresh的方法手动调用以执行observeSize
			 * 如果中间插槽有margin须提前指定，margin无法被组件计算在宽高内
			 */
			observeSize(margin = this.observeMargin) {
				const {top = 0, left = 0, bottom = 0, right = 0} = margin || {}
				this.core.delayAction(() => {
					this.core.component.query('#center', this).then(res => {
						if (res) {
							var {width, height} = res
							width += left + right
							height += top + bottom
							const {frame} = this.material[1][1]
							if (frame.width < width) {
								this.c_width = width
							}
							if (frame.height < height) {
								this.c_height = height
							}
						}
					})
				}, 100)
			},
			
			// 获取宽高的缩放值并计算九部分的基础数据
			getScaleValue() {
				var {width, height, size, sWidth, sHeight} = this
				if (width) {
					width = parseFloat(width)
				}
				if (height) {
					height = parseFloat(height)
				}
				if (size) {
					size = parseFloat(size)
					if (!width) {
						width = size
					}
					if (!height) {
						height = size
					}
				}
				if (!width && !height && !size) {
					// 一定要有值
					width = this.$parent.$el ? this.$parent.$el.offsetWidth : (this.$root.RealWidth || this.core.store.device.info.windowWidth)
				}
				if (this.frame) {
					const {x, y} = this.frame
					if (typeof(x) === 'string' && x.substr(-1) === '%') {
						frame.x = parseFloat(x.substr(0, x.length - 1)) * 0.01 * sWidth
					}
					if (typeof(y) === 'string' && y.substr(-1) === '%') {
						frame.y = parseFloat(y.substr(0, y.length - 1)) * 0.01 * sHeight
					}
					this.wScale = width / this.frame.width
					if (height) {
						this.hScale = height / this.frame.height
					} else {
						this.hScale = this.wScale
					}
				} else {
					// 一定有width
					this.wScale = width / sWidth
					if (height) {
						this.hScale = height / sHeight
					} else {
						this.hScale = this.wScale
					}
					// 计算九部分的基础数据
					this.getMaterialData()
				}
			},
			
			// 取最小值
			getMinValue(value1, value2) {
				if (value1 > value2) {
					return value2
				}
				return value1
			},
			
			// 计算九部分基础数据
			getMaterialData() {
				var {side, top, left, bottom, right, sWidth, sHeight} = this
				// side要修正，最多等于宽高的一半
				side = parseFloat(side)
				top = parseFloat(top)
				left = parseFloat(left)
				bottom = parseFloat(bottom)
				right = parseFloat(right)
				var bHeight = sHeight * 0.5
				var bWidth = sWidth * 0.5
				if (!top) {
					top = this.getMinValue(side, bHeight)
				}
				if (!left) {
					left = this.getMinValue(side, bWidth)
				}
				if (!bottom) {
					bottom = this.getMinValue(side, bHeight)
				}
				if (!right) {
					right = this.getMinValue(side, bWidth)
				}
				
				for (let i = 0; i < this.material.length; i++) {
					for (let j = 0; j < this.material[i].length; j++) {
						const item = this.material[i][j]
						item.canvasId = this.canvasId + item.key
						const {frame} = item
						switch (i){
							case 0:
								switch (j){
									case 0: // 左上角
										frame.x = 0
										frame.y = 0
										frame.width = left
										frame.height = top
										break
									case 1: // 上边
										frame.x = left
										frame.y = 0
										frame.width = sWidth - left - right
										frame.height = top
										break
									case 2: // 右上角
										frame.x = sWidth - right
										frame.y = 0
										frame.width = right
										frame.height = top
										break
								}
								break
							case 1:
								switch (j){
									case 0: // 左边
										frame.x = 0
										frame.y = top
										frame.width = left
										frame.height = sHeight - top - bottom
										break
									case 1: // 中心
										frame.x = left
										frame.y = top
										frame.width = sWidth - left - right
										frame.height = sHeight - top - bottom
										this.c_width = frame.width
										this.c_height = frame.height
										break
									case 2: // 右边
										frame.x = sWidth - right
										frame.y = top
										frame.width = right
										frame.height = sHeight - top - bottom
										break
								}
								break
							case 2:
								switch (j){
									case 0: // 左下角
										frame.x = 0
										frame.y = sHeight - bottom
										frame.width = left
										frame.height = bottom
										break
									case 1: // 下边
										frame.x = left
										frame.y = sHeight - bottom
										frame.width = sWidth - left - right
										frame.height = bottom
										break
									case 2: // 右下角
										frame.x = sWidth - right
										frame.y = sHeight - bottom
										frame.width = right
										frame.height = bottom
										break
								}
								break
						}
					}
				}
			},
			
			// 每部分完成后的方法
			drawComplete(imgUrl, item) {
				if (imgUrl) {
					if (item) {
						item.src = imgUrl
						if (item.key === 'center') {
							this.$emit('center', item, {wScale: this.wScale, hScale: this.hScale})
						}
					} else {
						if (this.frame) {
							this.iSrc = imgUrl
						}
					}
				}
				
				this.count ++
				if (this.count < 9) {
					
				} else {
					// 执行完毕
					this.step = 3
					this.count = 0
					this.observeSize()
				}
			},
			
			/**
			 * 绘制一张原图
			 * 方案一：
			 * 	从原图中获取对应的像素数据 - 这一步相当慢
			 * 	然后把像素数据放在九张新图中，获取新图。这一步行不通。该方案取消
			 * 方案二：
			 * 分别从中取对应部分
			 */
			drawAction(context, size, drawImage, callback, data, getTempImageUrl, instance) {
				drawImage(this.src).then(res => {
					const action = () => {
						const {sWidth, sHeight, frame} = this
						if (frame) {
							const {x = 0, y = 0, width = 100, height = 100} = frame || {}
							uni.canvasToTempFilePath({
								...data,
								x, y, width, height,
								destWidth: size(width), destHeight: size(height),
								success: (res) => {
									this.drawComplete(getTempImageUrl(res))
								},
								fail: (err) => {
									console.error(err)
								}
							}, instance)
						} else {
							// 分别只取对应的部分
							this.material.map(arr => {
								arr.map(item => {
									uni.canvasToTempFilePath({
										...data,
										x: item.frame.x, y: item.frame.y,
										width: item.frame.width, height: item.frame.height,
										destWidth: size(item.frame.width), destHeight: size(item.frame.height),
										success: (res) => {
											this.drawComplete(getTempImageUrl(res), item)
										},
										fail: (err) => {
											console.error(err)
											this.drawComplete()
										}
									}, instance)
								})
							})
						}
					}
					// #ifdef MP-WEIXIN
					action()
					// #endif
					// #ifndef MP-WEIXIN
					context.draw(true, action)
					// #endif
				})
			},
			
			// 获取平铺的重复属性
			getRepeat(index, idx) {
				if (index === 1 && idx === 1) {
					// 最中间图片可垂直重复
					return 'repeat-y'
				} else if (index === 1) {
					// 中间的两侧图片，一定垂直重复
					return 'repeat-y'
				} else if (idx === 1) {
					// 中间的上下图片，一定是水平重复
					return 'repeat-x'
				}
				// 四个角一定不重复
				return 'no-repeat'
			},
			
			// 获取修正值样式
			reviseStyle(position) {
				const style = {}
				if (position === 'center') {
					
				} else {
					const arr = position.split(' ')
					arr.map(str => {
						style[`margin-${str}`] = this.revise * -1 + 'px'
					})
				}
				return style
			},
			
			// 获取真实的尺寸
			getRealSize(index, idx, type) {
				const scale = this[`${type.substr(0, 1)}Scale`] || 1
				if (this[`c_${type}`]) {
					if (type === 'width' && idx === 1) {
						return this.c_width * scale + 'px'
					}
					if (type === 'height' && index === 1) {
						return this.c_height * scale + 'px'
					}
				}
				const res = this.material[index][idx].frame[type] * scale
				return res + 'px'
			},
			
			/**
			 * 获取内容的间距
			 * 由于监听的尺寸无法获取最顶和最底的marginTop和marginBottom，所以需要通过observeMargin插队过来变成padding
			 */
			getContentPadding() {
				const {top = 0, right = 0, bottom = 0, left = 0} = this.observeMargin || {}
				return `${top}px ${right}px ${bottom}px ${left}px`
			},
		}
	}
</script>

<style>
	uni-image {
		display:inline-block;
	}
	
	image {
		display: block;
	}
</style>